Skip to content

Conversation

@numinit
Copy link

@numinit numinit commented Jan 5, 2026

Motivation

Using the Rust bindings (which call into the C bindings) to set eval-related settings was not working.

Since EvalSettings were defined in libcmd, we need to move them to libexpr if libexpr-c wants to use them. Since libcmd enforces a dependency on libflake, we also need to modify config-global to absorb duplicate registrations and support callbacks for customizing behavior that we only need in libcmd.

Note that we add back some of the global setting variables with this, but this does fix the C API.

Context

Implementation strategy, since this was slightly nontrivial:

  • Move registration for eval (and fetcher settings, which had the same issue but was easier to solve) to libexpr and libfetchers.
  • Absorb duplicate config registrations in config-global.cc.
  • Add a new GlobalConfig registration constructor allowing the caller to pass a callback; use this to break the hard libflake dependency.
  • Use pointers to the global eval and fetcher config in libexpr-c instead of creating new instances that are not bound to Nix's settings.

Upstream PR: NixOS#14917

Note that this was tested using the Rust bindings and setting eval-cores.

Summary by CodeRabbit

  • Refactor
    • Restructured internal settings registration and initialization to improve configuration management efficiency.
    • Improved C API settings handling with updated internal data structures.
    • Added support for configuration callbacks during the registration process.

✏️ Tip: You can customize this high-level summary in your review settings.

Using the Rust bindings (which call into the C bindings) to set
eval-related settings was not working.

Since EvalSettings were defined in libcmd, we need to move them to
libexpr if libexpr-c wants to use them. Since libcmd enforces a
dependency on libflake, we also need to modify config-global to absorb
duplicate registrations and support callbacks for customizing behavior
that we only need in libcmd.

Note that we add back some of the global setting variables with this,
but this does fix the C API.
@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

📝 Walkthrough

Walkthrough

This refactoring introduces a global evalSettings object in the expression evaluation module, converts C API settings fields from value types to pointer types, and adds a callback mechanism to GlobalConfig::Register to enable post-registration hooks. Settings are now stored by reference rather than copied, and initialization logic is deferred via callbacks.

Changes

Cohort / File(s) Summary
Global Settings Infrastructure
src/libexpr/include/nix/expr/eval-settings.hh, src/libexpr/eval-settings.cc
Added public global variable evalSettings with extern declaration; added include dependency on config-global.hh and internal static registration.
Config Registration System
src/libutil/include/nix/util/config-global.hh, src/libutil/config-global.cc
Added overloaded GlobalConfig::Register constructor accepting Config* and callback function; constructor delegates and invokes callback post-registration; added duplicate registration check.
C API Internal Structures
src/libexpr-c/nix_api_expr_internal.h
Changed nix_eval_state_builder and EvalState struct fields from value types to pointer types: settings and fetchSettings now store pointers instead of copies.
C API Implementation
src/libexpr-c/nix_api_expr.cc
Updated nix_eval_state_builder_new, _load, and _build functions to initialize and dereference pointer-based settings fields; adjusted all accesses to use pointer operators.
CLI Settings Registration
src/libcmd/common-eval-args.cc
Migrated rEvalSettings registration from aggregate initialization to callback-based registration; moved lookupPathHooks initialization into lambda passed to GlobalConfig::Register.
Flake C API Integration
src/libflake-c/nix_api_flake.cc
Updated nix_flake_settings_add_to_eval_state_builder to dereference pointer: changed argument from builder->settings to *builder->settings.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • cole-h

Poem

🐰 Pointers now hold what values once did store,
Callbacks spring forth from registration's door,
Settings no longer copied, but referenced true,
A cleaner architecture—fresh morning dew!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: resolving an issue where changing eval settings via the C API had no effect.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Fix all issues with AI Agents 🤖
In @src/libexpr/eval-settings.cc:
- Line 122: The global evalSettings currently binds to settings.readOnlyMode at
static init time, risking a dangling reference due to undefined init order
between evalSettings and settings; change evalSettings to be lazily initialized
(e.g., replace the global EvalSettings evalSettings with an accessor like
getEvalSettings() that creates a function-local static EvalSettings and reads
settings.readOnlyMode at first use) or store the readOnlyMode value (or a safe
pointer) inside EvalSettings instead of a reference; ensure the new code
references the existing EvalSettings type and the settings.readOnlyMode symbol
so callers use getEvalSettings() (or the new safe construct) everywhere
evalSettings was used.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/libcmd/common-eval-args.cc (1)

22-43: Callback pattern creates static initialization order dependency.

The callback mechanism cleanly separates libcmd concerns from libexpr, but the implementation has a critical flaw: the callback executes immediately in the GlobalConfig::Register constructor (not deferred), and it references the global fetchSettings variable (lines 28, 31). Since fetchSettings is defined in a separate translation unit (src/libfetchers/fetch-settings.cc), C++ provides no guarantee about initialization order—rEvalSettings could execute before fetchSettings is initialized, causing undefined behavior.

Either defer the callback execution until after all globals are initialized, or move the flake lookup hook registration to a location that guarantees both evalSettings and fetchSettings are already initialized.

🧹 Nitpick comments (1)
src/libutil/config-global.cc (1)

70-74: Consider callback ordering dependencies.

The callback is executed immediately after registration during static initialization. Ensure that callbacks don't depend on other configs being registered first, as static initialization order across translation units is undefined.

If callback ordering becomes an issue, consider deferring callback execution to a later initialization phase (e.g., after all static registrations complete). However, this is likely not necessary for the current use case.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51dacdd and 6142e85.

📒 Files selected for processing (8)
  • src/libcmd/common-eval-args.cc
  • src/libexpr-c/nix_api_expr.cc
  • src/libexpr-c/nix_api_expr_internal.h
  • src/libexpr/eval-settings.cc
  • src/libexpr/include/nix/expr/eval-settings.hh
  • src/libflake-c/nix_api_flake.cc
  • src/libutil/config-global.cc
  • src/libutil/include/nix/util/config-global.hh
🧰 Additional context used
🧬 Code graph analysis (2)
src/libexpr/include/nix/expr/eval-settings.hh (1)
src/libexpr/include/nix/expr/parallel-eval.hh (1)
  • evalSettings (50-50)
src/libutil/config-global.cc (1)
src/libutil/include/nix/util/config-global.hh (2)
  • Register (28-28)
  • Register (29-29)
🔇 Additional comments (11)
src/libexpr/include/nix/expr/eval-settings.hh (1)

415-418: LGTM! Clean global settings declaration.

The extern declaration is well-documented and follows common patterns for global configuration objects. The placement after evalStackSize is logical.

src/libexpr/eval-settings.cc (1)

124-124: LGTM! Static registration is appropriate.

The static registration using GlobalConfig::Register ensures that evalSettings is registered with the global configuration system during static initialization.

src/libutil/include/nix/util/config-global.hh (1)

29-29: LGTM! Clean callback overload declaration.

The new constructor overload with an rvalue reference callback parameter follows C++ best practices and enables post-registration hooks.

src/libutil/config-global.cc (1)

64-68: Duplicate-registration guard is not thread-safe but acceptable for static initialization.

The duplicate check using std::find prevents the same config from being registered multiple times. While not thread-safe (TOCTOU), this is acceptable since registrations occur during single-threaded static initialization.

src/libflake-c/nix_api_flake.cc (1)

35-35: The pointer dereference is safe and correct. builder->settings is initialized to point to the global static object nix::evalSettings and cannot be null. The usage pattern is consistent throughout the codebase.

Likely an incorrect or invalid review comment.

src/libexpr-c/nix_api_expr_internal.h (2)

16-17: LGTM! Pointer semantics enable shared global settings.

The change from value to pointer types allows the C API to reference the global evalSettings and fetchSettings objects, ensuring that modifications to these settings take effect. The lifetime dependency on globals is acceptable here.


25-26: LGTM! Consistent pointer semantics.

The pointer types here match the nix_eval_state_builder structure, maintaining consistency in how settings are passed from builder to the constructed EvalState.

src/libexpr-c/nix_api_expr.cc (4)

9-9: LGTM! Necessary include for fetchSettings global.

This include is required to access the nix::fetchSettings global object referenced in the initialization code.


138-145: LGTM! Global settings initialization enables the fix.

Initializing settings and fetchSettings to point to the global nix::evalSettings and nix::fetchSettings objects (lines 141-142) is the key change that allows C API modifications to take effect. This ensures all eval state builders share the same configuration.

Note that this introduces shared mutable state: modifications to these settings through one builder will affect all eval states using the global settings.


163-165: LGTM! Correct pointer dereferencing.

The pointer dereferences correctly access the global settings objects to:

  1. Propagate readOnlyMode from the global settings
  2. Load configuration into both eval and fetch settings

The code assumes the pointers are non-null, which is valid since they're initialized in nix_eval_state_builder_new to point to globals.


190-196: LGTM! Proper pointer propagation to EvalState.

The construction correctly:

  1. Passes the settings pointers from the builder (lines 192-193)
  2. Dereferences them for the nix::EvalState constructor (line 194)

This maintains the shared reference to global settings throughout the eval state's lifetime. Since the pointers reference globals with static storage duration, the lifetime dependencies are satisfied.

return settings.useXDGBaseDirectories ? getStateDir() / "defexpr" : getHome() / ".nix-defexpr";
}

EvalSettings evalSettings{settings.readOnlyMode};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the definition of the global settings object

# Search for the definition of settings global object
rg -n -C5 '^Settings settings' --type=cpp

# Also search for extern declarations
rg -n 'extern.*Settings.*settings' --type=cpp -g '*.hh' -g '*.h'

Repository: DeterminateSystems/nix-src

Length of output: 793


🏁 Script executed:

# Find EvalSettings class definition and constructor
rg -n 'class EvalSettings' --type=cpp -A 10

# Also search for the constructor implementation
rg -n 'EvalSettings::EvalSettings' --type=cpp -A 5

Repository: DeterminateSystems/nix-src

Length of output: 536


🏁 Script executed:

# Find EvalSettings class header definition
rg -n 'class EvalSettings' --type=cpp -B 5 -A 20 src/libexpr/

# Also check the header file
fd 'eval-settings.hh' -x cat {}

Repository: DeterminateSystems/nix-src

Length of output: 16493


🏁 Script executed:

# Check the Register call in eval-settings.cc around line 124
sed -n '120,130p' src/libexpr/eval-settings.cc

# Check what GlobalConfig::Register does and if it provides ordering guarantees
rg -n 'class.*Register|struct.*Register' src/libutil/ --type=cpp -A 5

Repository: DeterminateSystems/nix-src

Length of output: 638


🏁 Script executed:

# Find the Register implementation
rg -n 'GlobalConfig::Register::Register' src/libutil/ --type=cpp -A 10

# Also check if there's any lazy initialization or factory pattern
rg -n 'evalSettings' src/libexpr/eval-settings.cc -B 5 -A 5

Repository: DeterminateSystems/nix-src

Length of output: 1490


Address static initialization order dependency between evalSettings and settings.

The global evalSettings stores a reference to settings.readOnlyMode. Since settings is defined in src/libstore/globals.cc and evalSettings in src/libexpr/eval-settings.cc, the C++ standard leaves their initialization order undefined. If evalSettings initializes before settings, it will hold a dangling reference.

Consider using lazy initialization (e.g., a factory function or callback mechanism) to defer creation of evalSettings until settings is guaranteed to be initialized.

🤖 Prompt for AI Agents
In @src/libexpr/eval-settings.cc around line 122, The global evalSettings
currently binds to settings.readOnlyMode at static init time, risking a dangling
reference due to undefined init order between evalSettings and settings; change
evalSettings to be lazily initialized (e.g., replace the global EvalSettings
evalSettings with an accessor like getEvalSettings() that creates a
function-local static EvalSettings and reads settings.readOnlyMode at first use)
or store the readOnlyMode value (or a safe pointer) inside EvalSettings instead
of a reference; ensure the new code references the existing EvalSettings type
and the settings.readOnlyMode symbol so callers use getEvalSettings() (or the
new safe construct) everywhere evalSettings was used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant